type Handler<D> = (data: D) => void

export namespace EventEmitter {
  // tslint:disable-next-line:no-empty-interface
  export interface EmittableEvents { }
}

type EmittableEvents = EventEmitter.EmittableEvents

class EventListener {
  public emitter!: EventEmitter<any> | null
  public event!: string | null
  public handler!: Handler<any> | null
  public destroied: boolean = false
  constructor(event: string, handler: Handler<any>, emitter: EventEmitter<any>) {
    this.event = event
    this.handler = handler
    this.emitter = emitter
  }

  public unregister() {
    this.emitter!.off(this.event!, this)
    this.destroy()
  }

  public destroy(): boolean {
    if (this.destroied) {
      return false
    }
    this.handler = null
    this.destroied = true
    return true
  }
}

export class EventEmitter<E extends EmittableEvents = {}> {
  protected _events: {
    [eventName in keyof E]?: Array<EventListener | null>
  } = {}

  /**
   * 绑定一个由此对象发生的特定事件的处理器，类似原生 js 中的 addEventListener，可以监听的事件由形参 E 指定
   * @param event 事件名称，必须是形参 E 中的 key
   * @param handler 事件处理函数，在事件发生时会被调用，参数中的 data 的类型由形参决定
   */
  public on<N extends keyof E>(event: N, handler: Handler<E[N]>): EventListener
  /**
   * 绑定一个由此对象发生的特定事件的处理器，类似原生 js 中的 addEventListener，可以监听的事件由形参 E 指定
   * @param event 事件名称，必须是形参 E 中的 key
   * @param labels 事件标签，如有多个则用空格隔开。注销事件时可以通过指定特定标签来注销事件
   * @param handler 事件处理函数，在事件发生时会被调用，参数中的 data 的类型由形参决定   *
   */
  public on<N extends keyof E>(event: N, handler: Handler<E[N]>): EventListener
  public on<N extends keyof E>(event: N, handler?: Handler<E[N]>): EventListener {
    if (!handler) {
      throw new TypeError('invlaid handler! --EventEmitter')
    }
    if (!this._events[event]) {
      this._events[event] = []
    }
    const listener = new EventListener(event as string, handler, this)
    this._events[event]!.push(listener)
    return listener
  }

  /**
   * 发射一个事件，可以发送的事件由形参 E 指定，发射后响应的监听器会被调用
   * @param event 事件名称，必须是形参 E 中的 key
   * @param data 事件数据，类型由形参 E 指定
   */
  public emit<N extends keyof E>(event: N, data: E[N]): boolean {
    if (!this._events[event]) {
      return false
    }
    const remains: EventListener[] = []
    for (const listener of this._events[event] as any) {
      if (listener.destroied) {
        continue
      }
      remains.push(listener)
      listener.handler.call(this, data)
    }
    if (remains.length !== this._events[event]!.length) {
      this._events[event] = remains
    }
    return true
  }

  /**
   * 移除指定事件的一个或多个监听器（取决于传入的target）,如果成功移除，会返回 true
   * @param event 事件名称
   * @param target 移除对象，如果不填，则移除所有监听此事件的对象。target可以是一个 EventListener 或者一个 label
   */
  public off<N extends keyof E>(event: N, target?: EventListener | string): boolean {
    if (!this._events[event]) {
      return false
    }
    if (target) {
      if (target instanceof EventListener) {
        for (let arr = this._events[event], i = 0; i < arr!.length; i++) {
          const listener = arr![i]
          if (!(listener === target)) {
            continue
          }
          this._events[event] = this._events[event]!.filter((l) => {
            return l !== listener
          })
          if (this._events[event]!.length === 0) {
            delete (this._events[event])
          }
        }
        console.error(target)
        console.error(`Cannot find listenert of ${event} in ${this} --EventEmitter`)
      } else if (typeof target === 'string') {
        let remains: EventListener[]
        for (let arr = this._events[event], i = 0; i < arr!.length; i++) {
          const listener = arr![i]
          let found: boolean = false
          if (!found) {
            remains!.push(listener!)
          } else {
            listener!.destroy()
          }
          this._events[event] = remains!
          if (this._events[event]!.length === 0) {
            delete this._events[event]
          }
        }
      } else {
        for (let arr = this._events[event], i = 0; i < arr!.length; i++) {
          const listener = arr![i]
          this._events[event]![i] = null
          listener!.destroy()
        }
        delete this._events[event]
      }
    }
    return true
  }

  public removeAllListeners() {
    for (const name in this._events) {
      const events = this._events[name]
      for (let arr = events, i = 0; i < arr!.length; i++) {
        events![i] = null
      }
      this._events[name] = []
    }
  }
}
